package com.nulldev.util.web.HttpServer3;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.data.Sets.DataSet;
import com.nulldev.util.data.Sets.DataTriplet;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.io.extensions.copyable.ClosingCopyable;
import com.nulldev.util.io.extensions.copyable.Copyable;
import com.nulldev.util.io.extensions.copyable.nio.ClosingNIOCopyable;
import com.nulldev.util.io.extensions.copyable.nio.NIOCopyable;
import com.nulldev.util.web.HttpServer3.pages.v3.HttpPage3;
import com.nulldev.util.web.HttpServer3.util.HttpServerUtil;
import com.nulldev.util.web.URLUtils.Entry;
import com.nulldev.util.web.URLUtils.nStreamHandlerFactory;
import com.nulldev.util.web.URLUtils.nStreamHandlerFactory.BlankInputStream;

public class Response implements Closeable {
	private static final BlankInputStream blank_input_stream = nStreamHandlerFactory.BLANK;
	private static final Copyable blank_copyable = HttpPage3.NOOP_COPYABLE;
	private static final NIOCopyable blank_nio_copyable = HttpPage3.NOOP_NIO_COPYABLE;

	private InputStream is;
	private Copyable copyable;
	private NIOCopyable nioCopyable;
	private final int response;
	private final Map<String, String> headers = FastMaps.map(0);
	private final Map<String, String> preloads = FastMaps.map(0);
	private volatile String customMime = null;

	public final AtomicBoolean isNativeEncoded = new AtomicBoolean();
	public volatile String nativeEncoding;
	private final AtomicBoolean deferCompression = new AtomicBoolean();
	private final AtomicBoolean disableKeepAlive = new AtomicBoolean();
	private final AtomicBoolean preferChunking = new AtomicBoolean();

	public Response(final int response) {
		this.response = response;
		this.is = blank_input_stream;
		this.copyable = blank_copyable;
		this.nioCopyable = blank_nio_copyable;
	}

	public boolean hasHeader(final String string) {
		return this.headers.containsKey(string);
	}

	public boolean hasPreloads() {
		return !this.preloads.isEmpty();
	}

	public Response(final int response, final InputStream is) {
		this.response = response;
		this.is = Variables.requireNonNullObject(is, "is");
	}

	public Response(final int response, final Copyable copyable) {
		this.response = response;
		this.copyable = Variables.requireNonNullObject(copyable, "copyable");
	}

	public Response(final int response, final Copyable copyable, final NIOCopyable nioCopyable) {
		this.response = response;
		this.copyable = Variables.requireNonNullObject(copyable, "copyable");
		this.nioCopyable = Variables.requireNonNullObject(nioCopyable, "nioCopyable");
	}

	public Response(final NIOCopyable nioCopyable, final int response) {
		this.response = response;
		this.nioCopyable = Variables.requireNonNullObject(nioCopyable, "nioCopyable");
	}

	public Response(final DataSet<Integer, InputStream> resp) {
		Variables.requireNonNull(resp, "resp");
		this.is = resp.getB();
		this.response = resp.getA();
	}

	public Map<String, String> getC() {
		return this.headers;
	}

	public Response(final DataTriplet<Integer, InputStream, Map<String, String>> resp) {
		Variables.requireNonNull(resp, "resp");
		this.is = resp.getB();
		this.response = resp.getA();
		this.headers.putAll(resp.getC());
	}

	public int getA() {
		return this.response;
	}

	public int getResponseCode() {
		return this.response;
	}

	public InputStream getB() {
		return this.is;
	}

	public InputStream getInputStream() {
		return this.is;
	}

	public Copyable getCopyable() {
		return this.copyable;
	}

	public NIOCopyable getNIOCopyable() {
		return this.nioCopyable;
	}

	public boolean copyable() {
		return this.copyable != null;
	}

	public boolean nioCopyable() {
		return this.nioCopyable != null;
	}

	public String getCustomMime() {
		return this.customMime;
	}

	public boolean hasCustomMime() {
		return this.customMime != null;
	}

	public Response setCustomMime(final String mime) {
		this.customMime = Variables.requireNonNullObject(mime, "mime");
		return this;
	}

	public Map<String, String> getCustomHeaders() {
		return this.headers;
	}

	public Response setNativeEncoding(final boolean enabled, final String encoding) {
		if (encoding != null && !encoding.isEmpty()) {
			this.isNativeEncoded.set(enabled);
			this.nativeEncoding = encoding;
		}
		return this;
	}

	public Response header(final String key, final String value) {
		if ((key != null && !key.isEmpty()) && (value != null && !value.isEmpty())) {
			this.headers.put(key, value);
		}
		return this;
	}

	public Response header(final String key, final int value) {
		if ((key != null && !key.isEmpty())) {
			this.headers.put(key, String.valueOf(value));
		}
		return this;
	}

	public Response header(final String key, final long value) {
		if ((key != null && !key.isEmpty())) {
			this.headers.put(key, String.valueOf(value));
		}
		return this;
	}

	public Response headers(final List<Entry> responses) {
		for (final Entry e : Variables.requireNonNullObject(responses, "responses")) {
			if (e.isValid()) {
				this.headers.put(e.getKey(), e.getValue());
			}
		}
		return this;
	}

	public Response deferCompression(final boolean value) {
		this.deferCompression.set(value);
		return this;
	}

	public boolean shouldDeferCompression() {
		return this.deferCompression.get();
	}

	public Response requestKeepAliveDisable(final boolean value) {
		this.disableKeepAlive.set(value);
		return this;
	}

	public boolean shouldDisableKeepAlive() {
		return this.disableKeepAlive.get();
	}

	public Response preferChunking(final boolean value) {
		this.preferChunking.set(value);
		return this;
	}

	public boolean shouldPreferChunking() {
		return this.preferChunking.get();
	}

	@Override
	public void close() throws IOException {
		IOUtils.closeQuietly(this.is);
		if (this.copyable instanceof ClosingCopyable) {
			IOUtils.closeQuietly((ClosingCopyable) this.copyable);
		}
		if (this.nioCopyable instanceof ClosingNIOCopyable) {
			IOUtils.closeQuietly((ClosingNIOCopyable) this.nioCopyable);
		}
		this.headers.clear();

	}

	public Response headers(final Map<String, List<String>> headers) {
		if (headers == null || headers.isEmpty())
			return this;
		for (final Map.Entry<String, List<String>> header : headers.entrySet()) {
			final List<String> s = header.getValue();
			this.header(header.getKey(), ArrayUtils.join(s.toArray(new String[s.size()]), ";"));
		}
		return this;
	}

	public Response preload(final String url, final String as) {
		if (url == null || url.isEmpty())
			return this;
		if (as == null || as.isEmpty())
			return this;
		this.preloads.put(url, as);
		return this;
	}

	public Map<String, String> preloads() {
		return this.preloads;
	}

	public boolean __has_header(final String headerName) {
		return HttpServerUtil.__has_header(this.headers, headerName);
	}

	@Override
	public String toString() {
		return "Response[hash=" + MathUtil.toHex(super.hashCode()) + ",io=" + this.is + ",copyable=" + this.copyable + ",nioCopyable=" + this.nioCopyable + "]";
	}
}
